#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
//第15讲：深⼊理解指针(5)
//1. sizeof和strlen的对⽐
//1.1 sizeof
//sizeof是单目操作符，不是函数！！！！！只有一个操作数
//sizeof 只关注占⽤内存空间的⼤⼩，不在乎内存中存放什么数据。
//int main()
//{
//	int a = 0;
//	printf("%zd", sizeof a);//如果sizeof中是一个变量的话可以不需要小括号，侧面证明了sizeof不是函数而是操作符
//	int a = 2;
//	short s = 4;
//	printf("%zd\n", sizeof(s = a + 2));//sizeof中的表达式不参与运算
//	printf("%d\n", s);
//	return 0;
//}

//1.2 strlen
//strlen 是C语⾔库函数，功能是求字符串⻓度。函数原型如下：
// size_t strlen ( const char * str );
//统计的是从 strlen 函数的参数 str 中这个地址开始向后， \0 之前字符串中字符的个数。
//strlen 函数会⼀直向后找 \0 字符，直到找到为⽌，所以可能存在越界查找。
//int main()
//{
//	size_t len = strlen("abcdef");//"abcdef"实际上是指向字符a的字符指针
//	printf("%zd\n", len);
//	//注意一种情况strlen求出来的是随机值:
//	char arr[] = { 'a','b','c' };
//	printf("%zd\n", strlen(arr));//数组里没有\0，会一直往后数，直到遇到\0，这个产生的是随机值
//	return 0;
//}

//2. 数组和指针笔试题解析
//2.1 ⼀维数组
//int main()
//{
//	int a[] = { 1,2,3,4 };
//	printf("%zd\n", sizeof(a));//sizeof(数组名)应用场景，计算的是整个数组的大小-- 16
//	printf("%zd\n", sizeof(a + 0));//int*+0 == int* 指针类型 -- 4/8
//	printf("%zd\n", sizeof(*a));//对首元素进行解引用得到int类型 -- 4 
//	printf("%zd\n", sizeof(a + 1));//首地址+1得到的还是int*类型 -- 4/8
//	printf("%zd\n", sizeof(a[1]));//首元素int -- 4
//	printf("%zd\n", sizeof(&a));//整个数组的地址，依然是地址,类型为int(*)[4] -- 4/8
//	printf("%zd\n", sizeof(*&a));//两种理解方式:
//								//1.*和&相互抵消得到a，求的就是整个数组的大小，相当于sizeof(a) -- 16
//								//2.先对a取地址 &a 得到int(*)[4](数组指针),再对其进行解引用访问的是整个数组 -- 16
//	printf("%zd\n", sizeof(&a + 1));//数组指针+1还是指针类型，因为不对其进行访问，虽然是野指针但是没有危险 -- 4/8
//	printf("%zd\n", sizeof(&a[0]));//首元素的地址 -- 4/8
//	printf("%zd\n", sizeof(&a[0] + 1));// 第二个元素的地址 -- 4/8
//	return 0;
//}

//2.2 字符数组
//代码1：
//int main()
//{
	//char arr[] = { 'a','b','c','d','e','f' };
	//printf("%zd\n", sizeof(arr));//sizeof(数组名)的应用，计算整个数组的大小 -- 6
	//printf("%zd\n", sizeof(arr + 0));//首地址+0是指针类型 -- 4/8
	//printf("%zd\n", sizeof(*arr));//对首地址解引用得到第一个元素char类型 -- 1
	//printf("%zd\n", sizeof(arr[1]));//首元素char类型 -- 1
	//printf("%zd\n", sizeof(&arr));//&arr的应用，取出整个数组的地址，是char(*)[6]类型，数组指针类型 -- 4/8
	//printf("%zd\n", sizeof(&arr + 1));//数组指针+1依然是指针char* 字符指针 -- 4/8
	//printf("%zd\n", sizeof(&arr[0] + 1));//首地址+1 char*字符指针类型，指向第[1]个元素 -- 4/8
//	return 0;
//}

//代码2：
//int main()
//{
//	char arr[] = { 'a','b','c','d','e','f' };
//	printf("%zd\n", strlen(arr));//strlen(指针)，向后查找元素，直到遇到\0,arr中无\0，越界访问 -- 随机值 假设是x
//	printf("%zd\n", strlen(arr + 0));//同上 -- 随机值 x
//	//printf("%zd\n", strlen(*arr));//对首地址解引用得到首元素'a','a'的ASCII码值是97，编号97的地址不可访问,strlen得到的是野指针
//								//报错信息:读取位置 0x0000000000000061 时发生访问冲突 十六进制的61就是97
//	//printf("%zd\n", strlen(arr[1]));//'b'的ASCII码值98，访问编号为98的地址，造成访问冲突，这也是个野指针
//	printf("%zd\n", strlen(&arr));//&arr是数组的地址，起始位置是首元素,还是一个一个往后走的,不是说走一下就走了6格距离 -- 随机值 x
//	printf("%zd\n", strlen(&arr + 1));//随机值 x-6
//	printf("%zd\n", strlen(&arr[0] + 1));//第二个元素的地址开始计数，直到遇到\0 -- 随机值x-1
//	return 0;
//}

//代码3：
//int main()
//{
//	char arr[] = "abcdef";//有\0 sizeof只在乎你提供的地址里的元素有多大，和类型也有关系
//	printf("%zd\n", sizeof(arr));//sizeof(arr)的应用，计算整个数组的大小，加上最后的\0 -- 7
//	printf("%zd\n", sizeof(arr + 0));//arr+0表示数组首元素的地址,char*类型,也就是指针 -- 4/8
//	printf("%zd\n", sizeof(*arr));//首元素'a',char类型 -- 1
//	printf("%zd\n", sizeof(arr[1]));//第[1]个元素'b',char类型 -- 1
//	printf("%zd\n", sizeof(&arr));//取出整个数组的地址,是数组指针类型 -- 4/8 
//	printf("%zd\n", sizeof(&arr + 1));//整个元素的地址+1还是数组指针类型 -- 4/8
//	printf("%zd\n", sizeof(&arr[0] + 1));//首元素地址+1是指针类型char* -- 4/8
//	return 0;
//}

//代码4：
//int main()
//{
//	char arr[] = "abcdef";
//	printf("%d\n", strlen(arr));//strlen会向后访问,首元素地址向后访问直到遇到\0 -- 6
//	printf("%d\n", strlen(arr + 0));//首元素地址 -- 6
//	//printf("%d\n", strlen(*arr));//访问‘a’对应的ASCII 97编号的地址 -- 非法访问
//	//printf("%d\n", strlen(arr[1]));//访问‘b’对应的ASCII 98编号的地址 -- 非法访问
//	printf("%d\n", strlen(&arr));//整个数组的地址，但起始地址依然是首元素的地址,向后一个字节一个字节的数 -- 6
//	printf("%d\n", strlen(&arr + 1));//跳过整个元素向后访问 -- 随机值
//	printf("%d\n", strlen(&arr[0] + 1));//跳过第一个元素向后访问 -- 5
//	return 0;
//}

//代码5：
//int main()
//{
//	char* p = "abcdef";//p指向的是第一个字符的地址，也就是‘a’的地址
//	printf("%d\n", sizeof(p));//p的类型是char* -- 4/8
//	printf("%d\n", sizeof(p + 1));//char* -- 4/8
//	printf("%d\n", sizeof(*p));// 'a' char -- 1
//	printf("%d\n", sizeof(p[0]));// 'a' char --1
//	printf("%d\n", sizeof(&p));//char** --4/8
//	printf("%d\n", sizeof(&p + 1));//char** -- 4/8
//	printf("%d\n", sizeof(&p[0] + 1));// char* -- 4/8
//	return 0;
//}

//代码6：
//int main()
//{
//	//1.可以把常量字符串想象成数组(但又有不同)
//	//2.p可以理解为数组名,p[0]就代表首元素
//	char* p = "abcdef";//指向'a'
//	//这就相当于:
//	//char p[]="abcdef"
//	printf("%zd\n", strlen(p));//p指向'a'的地址 -- 6
//	printf("%zd\n", strlen(p + 1));// -- 5
//	//printf("%zd\n", strlen(*p));//'a' -- 非法
//	printf("%zd\n", strlen(&p));//&p是指针变量的地址，和“abcdef”没有神魔关系了 -- 随机值
//								//从p这个指针变量的起始位置开始往后一个字节一个字节向后数
//	printf("%zd\n", strlen(&p + 1));//从&p+1的位置向后数，和"abcdef"仍然没有关系 -- 随机值
//									//那这个值和上面那个值有没有什么关系呢?
//									//有可能有：
//									//1.有:&p向后4个或8个字节里没有遇到\0的话 &p就比&p+1多4或8
//									//2.没有:&p向后4个或8个字节里遇到\0的话，&p和&p+1就没有关系,并且strlen(&p)的值就小于4或者8
//	printf("%zd\n", strlen(&p[0] + 1));// -- 5
//	return 0;
//}

//2.3 ⼆维数组
//int main()
//{
//	// a[0]是数组首元素的地址，也就是第一行首元素的地址
//	int a[3][4] = { 0 };
//	printf("%d\n", sizeof(a));//sizeof(a)应用，表示整个数组 -- 48
//	printf("%d\n", sizeof(a[0][0]));//首元素int -- 4
//	printf("%d\n", sizeof(a[0]));//第一行数组名单独放在sizeof里，求的是第一行数组总大小 -- 16 
//	printf("%d\n", sizeof(a[0] + 1));//a[0]并没有单独放在sizeof内部,所以这里的数组名a[0]就是数组首元素的地址等价于&a[0][0],+1后就是&a[0][1],int*类型 -- 4/8
//	printf("%d\n", sizeof(*(a[0] + 1)));//对&a[0][1]解引用得到a[0][1],int类型 -- 4
//	printf("%d\n", sizeof(a + 1));//数组名没有单独放在sizeof里面,表示整个第二行的地址不计算里面所有元素的大小int(*)[4]数组指针 -- 4/8
//	printf("%d\n", sizeof(*(a + 1)));//a==&a[0] &a[0]+1==&a[1] *&a[1]==a[1],sizeof里面单独放a[1]计算整个第二行大小 -- 16
//	printf("%d\n", sizeof(&a[0] + 1));//第一行的地址+1得到第二行的地址&a[1],int(*)[4] -- 4/8
//	printf("%d\n", sizeof(*(&a[0] + 1)));//相当于*(&a[1])==a[1] -- 16
//	printf("%d\n", sizeof(*a));//*(&a[0])==a[0] -- 16
//	printf("%d\n", sizeof(a[3]));//数组名单独放在sizeof里,计算整个第三行元素大小,无需真实存在，仅仅通过类型int [4]的推断就能计算出长度 -- 16 -- 4*sizeof(int)
//	printf("%d\n", sizeof(&a[0]));//int(*)[4]数组指针 -- 4/8
//	return 0;
//}

//数组名的意义：
//1. sizeof(数组名)，这⾥的数组名表⽰整个数组，计算的是整个数组的⼤⼩。
//2. & 数组名，这⾥的数组名表⽰整个数组，取出的是整个数组的地址。
//3. 除此之外所有的数组名都表⽰⾸元素的地址。


//3. 指针运算笔试题解析
//3.1 题⽬1：
//int main()
//{
//	int a[5] = { 1, 2, 3, 4, 5 };
//	int* ptr = (int*)(&a + 1);//&a是指向整个数组的数组指针，类型是int(*)[5],+1后跨过一整个数组
//	printf("%d,%d", *(a + 1), *(ptr - 1));
//	return 0;				//ptr-1后向前移动四个字节指向5
//}
//注意类型!!!
//类型决定了+1 -1 时到底走向哪里

//3.2 题⽬2
//在X86环境下
//假设结构体的⼤⼩是20个字节
//程序输出的结果是啥？
//struct Test							   //struct Test
//{									   //{
//	int Num;						   //	int Num;
//	char* pcName;					   //	char* pcName;
//	short sDate;					   //	short sDate;	--->  是结构体指针
//	char cha[2];					   //	char cha[2];
//	short sBa[4];					   //	short sBa[4];
//}*p = (struct Test*)0x100000;		   //}*						
//int main()
//{
//	printf("%p\n", p + 0x1);
//	printf("%p\n", (unsigned long)p + 0x1);
//	printf("%p\n", (unsigned int*)p + 0x1);
//	return 0;
//}
//考察的是指针+-整数的操作 取决于指针的类型


//3.3 题⽬3
//int main()
//{
//	int a[3][2] = { (0, 1), (2, 3), (4, 5) };//我草，芝士逗号表达式，我被坑了
//											 //这个数组实际上是:
//											 //{ 1 , 3 }
//											 //{ 5 , 0 }
//											 //{ 0 , 0 }
//	int* p;
//	p = a[0];//不是sizeof里的数组名字，那这个就是 p=&a[0][0]
//	printf("%d", p[0]);//相当于:*(p+0) == *(&a[0][0]) == a[0][0] --> 1
//	return 0;
//}